Passed
Branch v8.x (a9c55b)
by Rafael S.
04:46 queued 01:17
created

wavio.js ➔ correctContainer_   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 2
c 1
b 0
f 0
nc 2
dl 0
loc 3
rs 10
nop 0
1
/*
2
 * Copyright (c) 2017-2018 Rafael da Silva Rocha.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining
5
 * a copy of this software and associated documentation files (the
6
 * "Software"), to deal in the Software without restriction, including
7
 * without limitation the rights to use, copy, modify, merge, publish,
8
 * distribute, sublicense, and/or sell copies of the Software, and to
9
 * permit persons to whom the Software is furnished to do so, subject to
10
 * the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be
13
 * included in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
25
/**
26
 * @fileoverview A class to read and write wav data.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
import riffChunks from '../vendor/riff-chunks.js';
31
import {pack, packStringTo, packTo, packString,} from '../vendor/byte-data.js';
32
33
// @type {BufferIO}  
34
import BufferIO from './bufferio.js';
35
// @type {WavStruct}
36
import WavStruct from './wavstruct.js';
37
38
/**
39
 * A class to read and write wav data.
40
 * @extends WavStruct
41
 */
42
export default class WavIO extends WavStruct {
43
44
  constructor() {
45
    super();
46
    /**
47
     * @type {!Object}
48
     * @private
49
     */
50
    this.uInt16_ = {bits: 16, be: false};
51
    /**
52
     * @type {!Object}
53
     * @private
54
     */
55
    this.uInt32_ = {bits: 32, be: false};
56
    /**
57
     * The bit depth code according to the samples.
58
     * @type {string}
59
     */
60
    this.bitDepth = '0';
61
    /**
62
     * @type {!Object}
63
     * @private
64
     */
65
    this.dataType = {};
66
    this.io = new BufferIO();
67
  }
68
69
  /**
70
   * Return the bytes of the 'bext' chunk.
71
   * @return {!Array<number>} The 'bext' chunk bytes.
72
   * @private
73
   */
74
  getBextBytes_() {
75
    /** @type {!Array<number>} */
76
    let bytes = [];
77
    this.enforceBext_();
78
    if (this.bext.chunkId) {
79
      this.bext.chunkSize = 602 + this.bext.codingHistory.length;
80
      bytes = bytes.concat(
81
        packString(this.bext.chunkId),
82
        pack(602 + this.bext.codingHistory.length, this.uInt32_),
83
        this.io.writeString_(this.bext.description, 256),
84
        this.io.writeString_(this.bext.originator, 32),
85
        this.io.writeString_(this.bext.originatorReference, 32),
86
        this.io.writeString_(this.bext.originationDate, 10),
87
        this.io.writeString_(this.bext.originationTime, 8),
88
        pack(this.bext.timeReference[0], this.uInt32_),
89
        pack(this.bext.timeReference[1], this.uInt32_),
90
        pack(this.bext.version, this.uInt16_),
91
        this.io.writeString_(this.bext.UMID, 64),
92
        pack(this.bext.loudnessValue, this.uInt16_),
93
        pack(this.bext.loudnessRange, this.uInt16_),
94
        pack(this.bext.maxTruePeakLevel, this.uInt16_),
95
        pack(this.bext.maxMomentaryLoudness, this.uInt16_),
96
        pack(this.bext.maxShortTermLoudness, this.uInt16_),
97
        this.io.writeString_(this.bext.reserved, 180),
98
        this.io.writeString_(
99
          this.bext.codingHistory, this.bext.codingHistory.length));
100
    }
101
    return bytes;
102
  }
103
104
  /**
105
   * Make sure a 'bext' chunk is created if BWF data was created in a file.
106
   * @private
107
   */
108
  enforceBext_() {
109
    for (var prop in this.bext) {
110
      if (this.bext.hasOwnProperty(prop)) {
111
        if (this.bext[prop] && prop != 'timeReference') {
112
          this.bext.chunkId = 'bext';
113
          break;
114
        }
115
      }
116
    }
117
    if (this.bext.timeReference[0] || this.bext.timeReference[1]) {
118
      this.bext.chunkId = 'bext';
119
    }
120
  }
121
122
  /**
123
   * Return the bytes of the 'ds64' chunk.
124
   * @return {!Array<number>} The 'ds64' chunk bytes.
125
   * @private
126
   */
127
  getDs64Bytes_() {
128
    /** @type {!Array<number>} */
129
    let bytes = [];
130
    if (this.ds64.chunkId) {
131
      bytes = bytes.concat(
132
        packString(this.ds64.chunkId),
133
        pack(this.ds64.chunkSize, this.uInt32_),
134
        pack(this.ds64.riffSizeHigh, this.uInt32_),
135
        pack(this.ds64.riffSizeLow, this.uInt32_),
136
        pack(this.ds64.dataSizeHigh, this.uInt32_),
137
        pack(this.ds64.dataSizeLow, this.uInt32_),
138
        pack(this.ds64.originationTime, this.uInt32_),
139
        pack(this.ds64.sampleCountHigh, this.uInt32_),
140
        pack(this.ds64.sampleCountLow, this.uInt32_));
141
    }
142
    //if (this.ds64.tableLength) {
143
    //  ds64Bytes = ds64Bytes.concat(
144
    //    pack(this.ds64.tableLength, this.uInt32_),
145
    //    this.ds64.table);
146
    //}
147
    return bytes;
148
  }
149
150
  /**
151
   * Return the bytes of the 'cue ' chunk.
152
   * @return {!Array<number>} The 'cue ' chunk bytes.
153
   * @private
154
   */
155
  getCueBytes_() {
156
    /** @type {!Array<number>} */
157
    let bytes = [];
158
    if (this.cue.chunkId) {
159
      /** @type {!Array<number>} */
160
      let cuePointsBytes = this.getCuePointsBytes_();
161
      bytes = bytes.concat(
162
        packString(this.cue.chunkId),
163
        pack(cuePointsBytes.length + 4, this.uInt32_),
164
        pack(this.cue.dwCuePoints, this.uInt32_),
165
        cuePointsBytes);
166
    }
167
    return bytes;
168
  }
169
170
  /**
171
   * Return the bytes of the 'cue ' points.
172
   * @return {!Array<number>} The 'cue ' points as an array of bytes.
173
   * @private
174
   */
175
  getCuePointsBytes_() {
176
    /** @type {!Array<number>} */
177
    let points = [];
178
    for (let i=0; i<this.cue.dwCuePoints; i++) {
179
      points = points.concat(
180
        pack(this.cue.points[i].dwName, this.uInt32_),
181
        pack(this.cue.points[i].dwPosition, this.uInt32_),
182
        packString(this.cue.points[i].fccChunk),
183
        pack(this.cue.points[i].dwChunkStart, this.uInt32_),
184
        pack(this.cue.points[i].dwBlockStart, this.uInt32_),
185
        pack(this.cue.points[i].dwSampleOffset, this.uInt32_));
186
    }
187
    return points;
188
  }
189
190
  /**
191
   * Return the bytes of the 'smpl' chunk.
192
   * @return {!Array<number>} The 'smpl' chunk bytes.
193
   * @private
194
   */
195
  getSmplBytes_() {
196
    /** @type {!Array<number>} */
197
    let bytes = [];
198
    if (this.smpl.chunkId) {
199
      /** @type {!Array<number>} */
200
      let smplLoopsBytes = this.getSmplLoopsBytes_();
201
      bytes = bytes.concat(
202
        packString(this.smpl.chunkId),
203
        pack(smplLoopsBytes.length + 36, this.uInt32_),
204
        pack(this.smpl.dwManufacturer, this.uInt32_),
205
        pack(this.smpl.dwProduct, this.uInt32_),
206
        pack(this.smpl.dwSamplePeriod, this.uInt32_),
207
        pack(this.smpl.dwMIDIUnityNote, this.uInt32_),
208
        pack(this.smpl.dwMIDIPitchFraction, this.uInt32_),
209
        pack(this.smpl.dwSMPTEFormat, this.uInt32_),
210
        pack(this.smpl.dwSMPTEOffset, this.uInt32_),
211
        pack(this.smpl.dwNumSampleLoops, this.uInt32_),
212
        pack(this.smpl.dwSamplerData, this.uInt32_),
213
        smplLoopsBytes);
214
    }
215
    return bytes;
216
  }
217
218
  /**
219
   * Return the bytes of the 'smpl' loops.
220
   * @return {!Array<number>} The 'smpl' loops as an array of bytes.
221
   * @private
222
   */
223
  getSmplLoopsBytes_() {
224
    /** @type {!Array<number>} */
225
    let loops = [];
226
    for (let i=0; i<this.smpl.dwNumSampleLoops; i++) {
227
      loops = loops.concat(
228
        pack(this.smpl.loops[i].dwName, this.uInt32_),
229
        pack(this.smpl.loops[i].dwType, this.uInt32_),
230
        pack(this.smpl.loops[i].dwStart, this.uInt32_),
231
        pack(this.smpl.loops[i].dwEnd, this.uInt32_),
232
        pack(this.smpl.loops[i].dwFraction, this.uInt32_),
233
        pack(this.smpl.loops[i].dwPlayCount, this.uInt32_));
234
    }
235
    return loops;
236
  }
237
238
  /**
239
   * Return the bytes of the 'fact' chunk.
240
   * @return {!Array<number>} The 'fact' chunk bytes.
241
   * @private
242
   */
243
  getFactBytes_() {
244
    /** @type {!Array<number>} */
245
    let bytes = [];
246
    if (this.fact.chunkId) {
247
      bytes = bytes.concat(
248
        packString(this.fact.chunkId),
249
        pack(this.fact.chunkSize, this.uInt32_),
250
        pack(this.fact.dwSampleLength, this.uInt32_));
251
    }
252
    return bytes;
253
  }
254
255
  /**
256
   * Return the bytes of the 'fmt ' chunk.
257
   * @return {!Array<number>} The 'fmt' chunk bytes.
258
   * @throws {Error} if no 'fmt ' chunk is present.
259
   * @private
260
   */
261
  getFmtBytes_() {
262
    /** @type {!Array<number>} */
263
    let fmtBytes = [];
264
    if (this.fmt.chunkId) {
265
      return fmtBytes.concat(
266
        packString(this.fmt.chunkId),
267
        pack(this.fmt.chunkSize, this.uInt32_),
268
        pack(this.fmt.audioFormat, this.uInt16_),
269
        pack(this.fmt.numChannels, this.uInt16_),
270
        pack(this.fmt.sampleRate, this.uInt32_),
271
        pack(this.fmt.byteRate, this.uInt32_),
272
        pack(this.fmt.blockAlign, this.uInt16_),
273
        pack(this.fmt.bitsPerSample, this.uInt16_),
274
        this.getFmtExtensionBytes_());
275
    }
276
    throw Error('Could not find the "fmt " chunk');
277
  }
278
279
  /**
280
   * Return the bytes of the fmt extension fields.
281
   * @return {!Array<number>} The fmt extension bytes.
282
   * @private
283
   */
284
  getFmtExtensionBytes_() {
285
    /** @type {!Array<number>} */
286
    let extension = [];
287
    if (this.fmt.chunkSize > 16) {
288
      extension = extension.concat(
289
        pack(this.fmt.cbSize, this.uInt16_));
290
    }
291
    if (this.fmt.chunkSize > 18) {
292
      extension = extension.concat(
293
        pack(this.fmt.validBitsPerSample, this.uInt16_));
294
    }
295
    if (this.fmt.chunkSize > 20) {
296
      extension = extension.concat(
297
        pack(this.fmt.dwChannelMask, this.uInt32_));
298
    }
299
    if (this.fmt.chunkSize > 24) {
300
      extension = extension.concat(
301
        pack(this.fmt.subformat[0], this.uInt32_),
302
        pack(this.fmt.subformat[1], this.uInt32_),
303
        pack(this.fmt.subformat[2], this.uInt32_),
304
        pack(this.fmt.subformat[3], this.uInt32_));
305
    }
306
    return extension;
307
  }
308
309
  /**
310
   * Return the bytes of the 'LIST' chunk.
311
   * @return {!Array<number>} The 'LIST' chunk bytes.
312
   */
313
  getLISTBytes_() {
314
    /** @type {!Array<number>} */
315
    let bytes = [];
316
    for (let i=0; i<this.LIST.length; i++) {
317
      /** @type {!Array<number>} */
318
      let subChunksBytes = this.getLISTSubChunksBytes_(
319
          this.LIST[i].subChunks, this.LIST[i].format);
320
      bytes = bytes.concat(
321
        packString(this.LIST[i].chunkId),
322
        pack(subChunksBytes.length + 4, this.uInt32_),
323
        packString(this.LIST[i].format),
324
        subChunksBytes);
325
    }
326
    return bytes;
327
  }
328
329
  /**
330
   * Return the bytes of the sub chunks of a 'LIST' chunk.
331
   * @param {!Array<!Object>} subChunks The 'LIST' sub chunks.
332
   * @param {string} format The format of the 'LIST' chunk.
333
   *    Currently supported values are 'adtl' or 'INFO'.
334
   * @return {!Array<number>} The sub chunk bytes.
335
   * @private
336
   */
337
  getLISTSubChunksBytes_(subChunks, format) {
338
    /** @type {!Array<number>} */
339
    let bytes = [];
340
    for (let i=0; i<subChunks.length; i++) {
341
      if (format == 'INFO') {
342
        bytes = bytes.concat(
343
          packString(subChunks[i].chunkId),
344
          pack(subChunks[i].value.length + 1, this.uInt32_),
345
          this.io.writeString_(
346
            subChunks[i].value, subChunks[i].value.length));
347
        bytes.push(0);
348
      } else if (format == 'adtl') {
349
        if (['labl', 'note'].indexOf(subChunks[i].chunkId) > -1) {
350
          bytes = bytes.concat(
351
            packString(subChunks[i].chunkId),
352
            pack(
353
              subChunks[i].value.length + 4 + 1, this.uInt32_),
354
            pack(subChunks[i].dwName, this.uInt32_),
355
            this.io.writeString_(
356
              subChunks[i].value,
357
              subChunks[i].value.length));
358
          bytes.push(0);
359
        } else if (subChunks[i].chunkId == 'ltxt') {
360
          bytes = bytes.concat(
361
            this.getLtxtChunkBytes_(subChunks[i]));
362
        }
363
      }
364
      if (bytes.length % 2) {
365
        bytes.push(0);
366
      }
367
    }
368
    return bytes;
369
  }
370
371
  /**
372
   * Return the bytes of a 'ltxt' chunk.
373
   * @param {!Object} ltxt the 'ltxt' chunk.
374
   * @return {!Array<number>} The 'ltxt' chunk bytes.
375
   * @private
376
   */
377
  getLtxtChunkBytes_(ltxt) {
378
    return [].concat(
379
      packString(ltxt.chunkId),
380
      pack(ltxt.value.length + 20, this.uInt32_),
381
      pack(ltxt.dwName, this.uInt32_),
382
      pack(ltxt.dwSampleLength, this.uInt32_),
383
      pack(ltxt.dwPurposeID, this.uInt32_),
384
      pack(ltxt.dwCountry, this.uInt16_),
385
      pack(ltxt.dwLanguage, this.uInt16_),
386
      pack(ltxt.dwDialect, this.uInt16_),
387
      pack(ltxt.dwCodePage, this.uInt16_),
388
      this.io.writeString_(ltxt.value, ltxt.value.length));
389
  }
390
391
  /**
392
   * Return the bytes of the 'junk' chunk.
393
   * @return {!Array<number>} The 'junk' chunk bytes.
394
   * @private
395
   */
396
  getJunkBytes_() {
397
    /** @type {!Array<number>} */
398
    let bytes = [];
399
    if (this.junk.chunkId) {
400
      return bytes.concat(
401
        packString(this.junk.chunkId),
402
        pack(this.junk.chunkData.length, this.uInt32_),
403
        this.junk.chunkData);
404
    }
405
    return bytes;
406
  }
407
408
  /**
409
   * Return a .wav file byte buffer with the data from the WaveFile object.
410
   * The return value of this method can be written straight to disk.
411
   * @return {!Uint8Array} The wav file bytes.
412
   * @private
413
   */
414
  createWaveFile_() {
415
    /** @type {!Array<!Array<number>>} */
416
    let fileBody = [
417
      this.getJunkBytes_(),
418
      this.getDs64Bytes_(),
419
      this.getBextBytes_(),
420
      this.getFmtBytes_(),
421
      this.getFactBytes_(),
422
      packString(this.data.chunkId),
423
      pack(this.data.samples.length, this.uInt32_),
424
      this.data.samples,
425
      this.getCueBytes_(),
426
      this.getSmplBytes_(),
427
      this.getLISTBytes_()
428
    ];
429
    /** @type {number} */
430
    let fileBodyLength = 0;
431
    for (let i=0; i<fileBody.length; i++) {
432
      fileBodyLength += fileBody[i].length;
433
    }
434
    /** @type {!Uint8Array} */
435
    let file = new Uint8Array(fileBodyLength + 12);
436
    /** @type {number} */
437
    let index = 0;
438
    index = packStringTo(this.container, file, index);
439
    index = packTo(fileBodyLength + 4, this.uInt32_, file, index);
440
    index = packStringTo(this.format, file, index);
441
    for (let i=0; i<fileBody.length; i++) {
442
      file.set(fileBody[i], index);
443
      index += fileBody[i].length;
444
    }
445
    return file;
446
  }
447
448
  /**
449
   * Set up the WaveFile object from a byte buffer.
450
   * @param {!Uint8Array} buffer The buffer.
451
   * @param {boolean=} samples True if the samples should be loaded.
452
   * @throws {Error} If container is not RIFF, RIFX or RF64.
453
   * @throws {Error} If no 'fmt ' chunk is found.
454
   * @throws {Error} If no 'data' chunk is found.
455
   */
456
  readWavBuffer(buffer, samples=true) {
457
    this.io.head_ = 0;
458
    this.clearHeader_();
459
    this.readRIFFChunk_(buffer);
460
    /** @type {!Object} */
461
    let chunk = riffChunks(buffer);
462
    this.readDs64Chunk_(buffer, chunk.subChunks);
463
    this.readFmtChunk_(buffer, chunk.subChunks);
464
    this.readFactChunk_(buffer, chunk.subChunks);
465
    this.readBextChunk_(buffer, chunk.subChunks);
466
    this.readCueChunk_(buffer, chunk.subChunks);
467
    this.readSmplChunk_(buffer, chunk.subChunks);
468
    this.readDataChunk_(buffer, chunk.subChunks, samples);
469
    this.readJunkChunk_(buffer, chunk.subChunks);
470
    this.readLISTChunk_(buffer, chunk.subChunks);
471
    this.bitDepthFromFmt_();
472
    this.updateDataType_();
473
  }
474
475
  /**
476
   * Read the RIFF chunk a wave file.
477
   * @param {!Uint8Array} bytes A wav buffer.
478
   * @throws {Error} If no 'RIFF' chunk is found.
479
   * @private
480
   */
481
  readRIFFChunk_(bytes) {
482
    this.io.head_ = 0;
483
    this.container = this.io.readString_(bytes, 4);
484
    if (['RIFF', 'RIFX', 'RF64'].indexOf(this.container) === -1) {
485
      throw Error('Not a supported format.');
486
    }
487
    this.LEorBE_();
488
    this.chunkSize = this.io.read_(bytes, this.uInt32_);
489
    this.format = this.io.readString_(bytes, 4);
490
    if (this.format != 'WAVE') {
491
      throw Error('Could not find the "WAVE" format identifier');
492
    }
493
  }
494
495
  /**
496
   * Read the 'fmt ' chunk of a wave file.
497
   * @param {!Uint8Array} buffer The wav file buffer.
498
   * @param {!Object} signature The file signature.
499
   * @throws {Error} If no 'fmt ' chunk is found.
500
   * @private
501
   */
502
  readFmtChunk_(buffer, signature) {
503
    /** @type {?Object} */
504
    let chunk = this.findChunk_(signature, 'fmt ');
505
    if (chunk) {
506
      this.io.head_ = chunk.chunkData.start;
507
      this.fmt.chunkId = chunk.chunkId;
508
      this.fmt.chunkSize = chunk.chunkSize;
509
      this.fmt.audioFormat = this.io.read_(buffer, this.uInt16_);
510
      this.fmt.numChannels = this.io.read_(buffer, this.uInt16_);
511
      this.fmt.sampleRate = this.io.read_(buffer, this.uInt32_);
512
      this.fmt.byteRate = this.io.read_(buffer, this.uInt32_);
513
      this.fmt.blockAlign = this.io.read_(buffer, this.uInt16_);
514
      this.fmt.bitsPerSample = this.io.read_(buffer, this.uInt16_);
515
      this.readFmtExtension_(buffer);
516
    } else {
517
      throw Error('Could not find the "fmt " chunk');
518
    }
519
  }
520
521
  /**
522
   * Read the 'fmt ' chunk extension.
523
   * @param {!Uint8Array} buffer The wav file buffer.
524
   * @private
525
   */
526
  readFmtExtension_(buffer) {
527
    if (this.fmt.chunkSize > 16) {
528
      this.fmt.cbSize = this.io.read_(buffer, this.uInt16_);
529
      if (this.fmt.chunkSize > 18) {
530
        this.fmt.validBitsPerSample = this.io.read_(buffer, this.uInt16_);
531
        if (this.fmt.chunkSize > 20) {
532
          this.fmt.dwChannelMask = this.io.read_(buffer, this.uInt32_);
533
          this.fmt.subformat = [
534
            this.io.read_(buffer, this.uInt32_),
535
            this.io.read_(buffer, this.uInt32_),
536
            this.io.read_(buffer, this.uInt32_),
537
            this.io.read_(buffer, this.uInt32_)];
538
        }
539
      }
540
    }
541
  }
542
543
  /**
544
   * Read the 'fact' chunk of a wav file.
545
   * @param {!Uint8Array} buffer The wav file buffer.
546
   * @param {!Object} signature The file signature.
547
   * @private
548
   */
549
  readFactChunk_(buffer, signature) {
550
    /** @type {?Object} */
551
    let chunk = this.findChunk_(signature, 'fact');
552
    if (chunk) {
553
      this.io.head_ = chunk.chunkData.start;
554
      this.fact.chunkId = chunk.chunkId;
555
      this.fact.chunkSize = chunk.chunkSize;
556
      this.fact.dwSampleLength = this.io.read_(buffer, this.uInt32_);
557
    }
558
  }
559
560
  /**
561
   * Read the 'cue ' chunk of a wave file.
562
   * @param {!Uint8Array} buffer The wav file buffer.
563
   * @param {!Object} signature The file signature.
564
   * @private
565
   */
566
  readCueChunk_(buffer, signature) {
567
    /** @type {?Object} */
568
    let chunk = this.findChunk_(signature, 'cue ');
569
    if (chunk) {
570
      this.io.head_ = chunk.chunkData.start;
571
      this.cue.chunkId = chunk.chunkId;
572
      this.cue.chunkSize = chunk.chunkSize;
573
      this.cue.dwCuePoints = this.io.read_(buffer, this.uInt32_);
574
      for (let i=0; i<this.cue.dwCuePoints; i++) {
575
        this.cue.points.push({
576
          dwName: this.io.read_(buffer, this.uInt32_),
577
          dwPosition: this.io.read_(buffer, this.uInt32_),
578
          fccChunk: this.io.readString_(buffer, 4),
579
          dwChunkStart: this.io.read_(buffer, this.uInt32_),
580
          dwBlockStart: this.io.read_(buffer, this.uInt32_),
581
          dwSampleOffset: this.io.read_(buffer, this.uInt32_),
582
        });
583
      }
584
    }
585
  }
586
587
  /**
588
   * Read the 'smpl' chunk of a wave file.
589
   * @param {!Uint8Array} buffer The wav file buffer.
590
   * @param {!Object} signature The file signature.
591
   * @private
592
   */
593
  readSmplChunk_(buffer, signature) {
594
    /** @type {?Object} */
595
    let chunk = this.findChunk_(signature, 'smpl');
596
    if (chunk) {
597
      this.io.head_ = chunk.chunkData.start;
598
      this.smpl.chunkId = chunk.chunkId;
599
      this.smpl.chunkSize = chunk.chunkSize;
600
      this.smpl.dwManufacturer = this.io.read_(buffer, this.uInt32_);
601
      this.smpl.dwProduct = this.io.read_(buffer, this.uInt32_);
602
      this.smpl.dwSamplePeriod = this.io.read_(buffer, this.uInt32_);
603
      this.smpl.dwMIDIUnityNote = this.io.read_(buffer, this.uInt32_);
604
      this.smpl.dwMIDIPitchFraction = this.io.read_(buffer, this.uInt32_);
605
      this.smpl.dwSMPTEFormat = this.io.read_(buffer, this.uInt32_);
606
      this.smpl.dwSMPTEOffset = this.io.read_(buffer, this.uInt32_);
607
      this.smpl.dwNumSampleLoops = this.io.read_(buffer, this.uInt32_);
608
      this.smpl.dwSamplerData = this.io.read_(buffer, this.uInt32_);
609
      for (let i=0; i<this.smpl.dwNumSampleLoops; i++) {
610
        this.smpl.loops.push({
611
          dwName: this.io.read_(buffer, this.uInt32_),
612
          dwType: this.io.read_(buffer, this.uInt32_),
613
          dwStart: this.io.read_(buffer, this.uInt32_),
614
          dwEnd: this.io.read_(buffer, this.uInt32_),
615
          dwFraction: this.io.read_(buffer, this.uInt32_),
616
          dwPlayCount: this.io.read_(buffer, this.uInt32_),
617
        });
618
      }
619
    }
620
  }
621
622
  /**
623
   * Read the 'data' chunk of a wave file.
624
   * @param {!Uint8Array} buffer The wav file buffer.
625
   * @param {!Object} signature The file signature.
626
   * @param {boolean} samples True if the samples should be loaded.
627
   * @throws {Error} If no 'data' chunk is found.
628
   * @private
629
   */
630
  readDataChunk_(buffer, signature, samples) {
631
    /** @type {?Object} */
632
    let chunk = this.findChunk_(signature, 'data');
633
    if (chunk) {
634
      this.data.chunkId = 'data';
635
      this.data.chunkSize = chunk.chunkSize;
636
      if (samples) {
637
        this.data.samples = buffer.slice(
638
          chunk.chunkData.start,
639
          chunk.chunkData.end);
640
      }
641
    } else {
642
      throw Error('Could not find the "data" chunk');
643
    }
644
  }
645
646
  /**
647
   * Read the 'bext' chunk of a wav file.
648
   * @param {!Uint8Array} buffer The wav file buffer.
649
   * @param {!Object} signature The file signature.
650
   * @private
651
   */
652
  readBextChunk_(buffer, signature) {
653
    /** @type {?Object} */
654
    let chunk = this.findChunk_(signature, 'bext');
655
    if (chunk) {
656
      this.io.head_ = chunk.chunkData.start;
657
      this.bext.chunkId = chunk.chunkId;
658
      this.bext.chunkSize = chunk.chunkSize;
659
      this.bext.description = this.io.readString_(buffer, 256);
660
      this.bext.originator = this.io.readString_(buffer, 32);
661
      this.bext.originatorReference = this.io.readString_(buffer, 32);
662
      this.bext.originationDate = this.io.readString_(buffer, 10);
663
      this.bext.originationTime = this.io.readString_(buffer, 8);
664
      this.bext.timeReference = [
665
        this.io.read_(buffer, this.uInt32_),
666
        this.io.read_(buffer, this.uInt32_)];
667
      this.bext.version = this.io.read_(buffer, this.uInt16_);
668
      this.bext.UMID = this.io.readString_(buffer, 64);
669
      this.bext.loudnessValue = this.io.read_(buffer, this.uInt16_);
670
      this.bext.loudnessRange = this.io.read_(buffer, this.uInt16_);
671
      this.bext.maxTruePeakLevel = this.io.read_(buffer, this.uInt16_);
672
      this.bext.maxMomentaryLoudness = this.io.read_(buffer, this.uInt16_);
673
      this.bext.maxShortTermLoudness = this.io.read_(buffer, this.uInt16_);
674
      this.bext.reserved = this.io.readString_(buffer, 180);
675
      this.bext.codingHistory = this.io.readString_(
676
        buffer, this.bext.chunkSize - 602);
677
    }
678
  }
679
680
  /**
681
   * Read the 'ds64' chunk of a wave file.
682
   * @param {!Uint8Array} buffer The wav file buffer.
683
   * @param {!Object} signature The file signature.
684
   * @throws {Error} If no 'ds64' chunk is found and the file is RF64.
685
   * @private
686
   */
687
  readDs64Chunk_(buffer, signature) {
688
    /** @type {?Object} */
689
    let chunk = this.findChunk_(signature, 'ds64');
690
    if (chunk) {
691
      this.io.head_ = chunk.chunkData.start;
692
      this.ds64.chunkId = chunk.chunkId;
693
      this.ds64.chunkSize = chunk.chunkSize;
694
      this.ds64.riffSizeHigh = this.io.read_(buffer, this.uInt32_);
695
      this.ds64.riffSizeLow = this.io.read_(buffer, this.uInt32_);
696
      this.ds64.dataSizeHigh = this.io.read_(buffer, this.uInt32_);
697
      this.ds64.dataSizeLow = this.io.read_(buffer, this.uInt32_);
698
      this.ds64.originationTime = this.io.read_(buffer, this.uInt32_);
699
      this.ds64.sampleCountHigh = this.io.read_(buffer, this.uInt32_);
700
      this.ds64.sampleCountLow = this.io.read_(buffer, this.uInt32_);
701
      //if (this.ds64.chunkSize > 28) {
702
      //  this.ds64.tableLength = unpack(
703
      //    chunkData.slice(28, 32), this.uInt32_);
704
      //  this.ds64.table = chunkData.slice(
705
      //     32, 32 + this.ds64.tableLength); 
706
      //}
707
    } else {
708
      if (this.container == 'RF64') {
709
        throw Error('Could not find the "ds64" chunk');  
710
      }
711
    }
712
  }
713
714
  /**
715
   * Read the 'LIST' chunks of a wave file.
716
   * @param {!Uint8Array} buffer The wav file buffer.
717
   * @param {!Object} signature The file signature.
718
   * @private
719
   */
720
  readLISTChunk_(buffer, signature) {
721
    /** @type {?Object} */
722
    let listChunks = this.findChunk_(signature, 'LIST', true);
723
    if (listChunks === null) {
724
      return;
725
    }
726
    for (let j=0; j < listChunks.length; j++) {
727
      /** @type {!Object} */
728
      let subChunk = listChunks[j];
729
      this.LIST.push({
730
        chunkId: subChunk.chunkId,
731
        chunkSize: subChunk.chunkSize,
732
        format: subChunk.format,
733
        subChunks: []});
734
      for (let x=0; x<subChunk.subChunks.length; x++) {
735
        this.readLISTSubChunks_(subChunk.subChunks[x],
736
          subChunk.format, buffer);
737
      }
738
    }
739
  }
740
741
  /**
742
   * Read the sub chunks of a 'LIST' chunk.
743
   * @param {!Object} subChunk The 'LIST' subchunks.
744
   * @param {string} format The 'LIST' format, 'adtl' or 'INFO'.
745
   * @param {!Uint8Array} buffer The wav file buffer.
746
   * @private
747
   */
748
  readLISTSubChunks_(subChunk, format, buffer) {
749
    if (format == 'adtl') {
750
      if (['labl', 'note','ltxt'].indexOf(subChunk.chunkId) > -1) {
751
        this.io.head_ = subChunk.chunkData.start;
752
        /** @type {!Object<string, string|number>} */
753
        let item = {
754
          chunkId: subChunk.chunkId,
755
          chunkSize: subChunk.chunkSize,
756
          dwName: this.io.read_(buffer, this.uInt32_)
757
        };
758
        if (subChunk.chunkId == 'ltxt') {
759
          item.dwSampleLength = this.io.read_(buffer, this.uInt32_);
760
          item.dwPurposeID = this.io.read_(buffer, this.uInt32_);
761
          item.dwCountry = this.io.read_(buffer, this.uInt16_);
762
          item.dwLanguage = this.io.read_(buffer, this.uInt16_);
763
          item.dwDialect = this.io.read_(buffer, this.uInt16_);
764
          item.dwCodePage = this.io.read_(buffer, this.uInt16_);
765
        }
766
        item.value = this.io.readZSTR_(buffer, this.io.head_);
767
        this.LIST[this.LIST.length - 1].subChunks.push(item);
768
      }
769
    // RIFF INFO tags like ICRD, ISFT, ICMT
770
    } else if(format == 'INFO') {
771
      this.io.head_ = subChunk.chunkData.start;
772
      this.LIST[this.LIST.length - 1].subChunks.push({
773
        chunkId: subChunk.chunkId,
774
        chunkSize: subChunk.chunkSize,
775
        value: this.io.readZSTR_(buffer, this.io.head_)
776
      });
777
    }
778
  }
779
780
  /**
781
   * Read the 'junk' chunk of a wave file.
782
   * @param {!Uint8Array} buffer The wav file buffer.
783
   * @param {!Object} signature The file signature.
784
   * @private
785
   */
786
  readJunkChunk_(buffer, signature) {
787
    /** @type {?Object} */
788
    let chunk = this.findChunk_(signature, 'junk');
789
    if (chunk) {
790
      this.junk = {
791
        chunkId: chunk.chunkId,
792
        chunkSize: chunk.chunkSize,
793
        chunkData: [].slice.call(buffer.slice(
794
          chunk.chunkData.start,
795
          chunk.chunkData.end))
796
      };
797
    }
798
  }
799
800
  /**
801
   * Reset attributes that should emptied when a file is
802
   * created with the fromScratch() or fromBuffer() methods.
803
   * @private
804
   */
805
  clearHeader_() {
806
    this.fmt.cbSize = 0;
807
    this.fmt.validBitsPerSample = 0;
808
    this.fact.chunkId = '';
809
    this.ds64.chunkId = '';
810
  }
811
812
  /**
813
   * Set up to work wih big-endian or little-endian files.
814
   * The types used are changed to LE or BE. If the
815
   * the file is big-endian (RIFX), true is returned.
816
   * @return {boolean} True if the file is RIFX.
817
   * @private
818
   */
819
  LEorBE_() {
820
    /** @type {boolean} */
821
    let bigEndian = this.container === 'RIFX';
822
    this.uInt16_.be = bigEndian;
823
    this.uInt32_.be = bigEndian;
824
    return bigEndian;
825
  }
826
827
  /**
828
   * Find a chunk by its fourCC_ in a array of RIFF chunks.
829
   * @param {!Object} chunks The wav file chunks.
830
   * @param {string} chunkId The chunk fourCC_.
831
   * @param {boolean} multiple True if there may be multiple chunks
832
   *    with the same chunkId.
833
   * @return {?Array<!Object>}
834
   * @private
835
   */
836
  findChunk_(chunks, chunkId, multiple=false) {
837
    /** @type {!Array<!Object>} */
838
    let chunk = [];
839
    for (let i=0; i<chunks.length; i++) {
840
      if (chunks[i].chunkId == chunkId) {
841
        if (multiple) {
842
          chunk.push(chunks[i]);
843
        } else {
844
          return chunks[i];
845
        }
846
      }
847
    }
848
    if (chunkId == 'LIST') {
849
      return chunk.length ? chunk : null;
850
    }
851
    return null;
852
  }
853
854
  /**
855
   * Update the type definition used to read and write the samples.
856
   * @private
857
   */
858
  updateDataType_() {
859
    /** @type {!Object} */
860
    this.dataType = {
861
      bits: ((parseInt(this.bitDepth, 10) - 1) | 7) + 1,
862
      float: this.bitDepth == '32f' || this.bitDepth == '64',
863
      signed: this.bitDepth != '8',
864
      be: this.container == 'RIFX'
865
    };
866
    if (['4', '8a', '8m'].indexOf(this.bitDepth) > -1 ) {
867
      this.dataType.bits = 8;
868
      this.dataType.signed = false;
869
    }
870
  }
871
872
  /**
873
   * Set the string code of the bit depth based on the 'fmt ' chunk.
874
   * @private
875
   */
876
  bitDepthFromFmt_() {
877
    if (this.fmt.audioFormat === 3 && this.fmt.bitsPerSample === 32) {
878
      this.bitDepth = '32f';
879
    } else if (this.fmt.audioFormat === 6) {
880
      this.bitDepth = '8a';
881
    } else if (this.fmt.audioFormat === 7) {
882
      this.bitDepth = '8m';
883
    } else {
884
      this.bitDepth = this.fmt.bitsPerSample.toString();
885
    }
886
  }
887
}
888